package com.gitee.wsl.lang.bytearray

import com.gitee.wsl.ext.array.concat
import com.gitee.wsl.ext.array.shiftLeft
import com.gitee.wsl.ext.array.shiftRight
import com.gitee.wsl.ext.base.Arrays.arraycopy
import com.gitee.wsl.lang.ByteOrder
import com.gitee.wsl.mathematics.function.random.SecureRandom
import kotlin.experimental.inv
import kotlin.jvm.JvmOverloads
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
import kotlin.random.Random


/**
 * Interface for transforming [Bytes]
 */
interface BytesTransformer {
    /**
     * Transform given victim in place, overwriting its internal byte array
     *
     * @param currentArray to preform the transformation on
     * @param inPlace      perform the operations directly on the victim's byte array to omit copying of the internal array
     * @return resulting bytes (either the overwritten instance or a new one)
     */
    fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray

    /**
     * If this transformer supports transformation without creation a new array
     *
     * @return true if supported
     */
    fun supportInPlaceTransformation(): Boolean

    /**
     * Simple transformer for bitwise operations on [Bytes] instances
     *
     * @see [Bitwise operation](https://en.wikipedia.org/wiki/Bitwise_operation.Bitwise_operators)
     */
    class BitWiseOperatorTransformer internal constructor(
        private val secondArray: ByteArray,
        private val mode: Mode,
    ) : BytesTransformer {

        enum class Mode {
            AND, OR, XOR
        }


        override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
            require(currentArray.size == secondArray.size) { "all byte array must be of same length doing bit wise operation" }

            val out = if (inPlace) currentArray else ByteArray(currentArray.size)

            for (i in currentArray.indices) {
                when (mode) {
                    Mode.AND -> out[i] = (currentArray[i].toInt() and secondArray[i].toInt()).toByte()
                    Mode.XOR -> out[i] = (currentArray[i].toInt() xor secondArray[i].toInt()).toByte()
                    Mode.OR -> out[i] = (currentArray[i].toInt() or secondArray[i].toInt()).toByte()
                    //else -> out[i] = (currentArray[i].toInt() or secondArray[i].toInt()).toByte()
                }
            }

            return out
        }

        override fun supportInPlaceTransformation(): Boolean {
            return true
        }
    }

    /**
     * Simple transformer for bitwise unary negation on [Bytes] instances
     *
     * @see [Bitwise operators: NOT](https://en.wikipedia.org/wiki/Bitwise_operation.NOT)
     */
    class NegateTransformer : BytesTransformer {
        override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
            val out: ByteArray =
                (if (inPlace) currentArray else Bytes.from(currentArray).array())

            for (i in out.indices) {
                out[i] = out[i].inv()
            }

            return out
        }

        override fun supportInPlaceTransformation(): Boolean {
            return true
        }
    }

    /**
     * Simple transformer for bit shifting [Bytes] instances
     *
     * @see [Bit shifts](https://en.wikipedia.org/wiki/Bitwise_operation.Bit_shifts)
     */
    class ShiftTransformer internal constructor(
        private val shiftCount: Int,
        private val type: Type,
        private val byteOrder: ByteOrder,
    ) : BytesTransformer {
        enum class Type {
            LEFT_SHIFT, RIGHT_SHIFT
        }

        override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
            val out = if (inPlace) currentArray else Bytes.from(currentArray).array()

            when (type) {
                Type.RIGHT_SHIFT ->  out.shiftRight( shiftCount, byteOrder)
                Type.LEFT_SHIFT ->  out.shiftLeft(shiftCount, byteOrder)
                //else -> return Util.Byte.shiftLeft(out, shiftCount, byteOrder)
            }
            return out
        }

        override fun supportInPlaceTransformation(): Boolean {
            return true
        }
    }

    /**
     * Simple transformer for bitwise operations on [Bytes] instances
     *
     * @see [Bitwise operation](https://en.wikipedia.org/wiki/Bitwise_operation.Bitwise_operators)
     */
    class ConcatTransformer internal constructor(private val secondArrays: ByteArray) : BytesTransformer {


        override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
            return currentArray.concat( secondArrays)
        }

        override fun supportInPlaceTransformation(): Boolean {
            return false
        }
    }

    /**
     * Reverses the internal byte array
     */
    class ReverseTransformer : BytesTransformer {
        override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
            val out: ByteArray =
                (if (inPlace) currentArray else com.gitee.wsl.lang.bytearray.Bytes.from(currentArray).array())
            return out.reversedArray()
        }

        override fun supportInPlaceTransformation(): Boolean {
            return true
        }
    }

    /**
     * Creates a new instance with a copy of the internal byte array and all other attributes.
     */
    class CopyTransformer internal constructor(val offset: Int, val length: Int) : BytesTransformer {
        override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
            val copy = ByteArray(length)
            arraycopy(currentArray, offset, copy, 0, copy.size)
            return copy
        }

        override fun supportInPlaceTransformation(): Boolean {
            return false
        }
    }

    /**
     * Copies the specified array, truncating or padding with zeros (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain `(byte)0`.
     *
     *
     * If the internal array will be increased, zero bytes will be added on the left,
     * keeping the value the same.
     */
    class ResizeTransformer internal constructor(private val newSize: Int, private val mode: Mode?) : BytesTransformer {
        enum class Mode {
            RESIZE_KEEP_FROM_ZERO_INDEX,
            RESIZE_KEEP_FROM_MAX_LENGTH
        }

        override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
            if (currentArray.size == newSize) {
                return currentArray
            }

            require(newSize >= 0) { "cannot resize to smaller than 0" }

            if (newSize == 0) {
                return ByteArray(0)
            }

            val resizedArray = ByteArray(newSize)

            if (mode == Mode.RESIZE_KEEP_FROM_MAX_LENGTH) {
                val max = max(0, abs(newSize - currentArray.size))

                if (newSize > currentArray.size) {
                    arraycopy(currentArray, 0, resizedArray, max, currentArray.size)
                } else {
                    arraycopy(currentArray, max, resizedArray, 0, newSize)
                }
            } else {
                arraycopy(currentArray, 0, resizedArray, 0, min(currentArray.size, resizedArray.size))
            }

            return resizedArray
        }

        override fun supportInPlaceTransformation(): Boolean {
            return false
        }
    }

    /**
     * Switches bits on specific position of an array
     */
    class BitSwitchTransformer internal constructor(private val position: Int, private val newBitValue: Boolean?) :
        BytesTransformer {
        override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
            val out = if (inPlace) currentArray else Bytes.from(currentArray).array()

            require(!(position < 0 || position >= 8 * currentArray.size)) { "bit index " + (position * 8) + " out of bounds" }

            val bytePosition = currentArray.size - 1 - position / 8
            if (newBitValue == null) {
                out[bytePosition] = (out[bytePosition].toInt() xor (1 shl position % 8)).toByte()
            } else if (newBitValue) {
                out[bytePosition] = (out[bytePosition].toInt() or (1 shl position % 8)).toByte()
            } else {
                out[bytePosition] = (out[bytePosition].toInt() and (1 shl position % 8).inv()).toByte()
            }
            return out
        }

        override fun supportInPlaceTransformation(): Boolean {
            return true
        }
    }

    companion object{

        /**
         * Create a [BytesTransformer] which shuffles the individual bytes in an array
         * with an [SecureRandom] instance.
         *
         * @return transformer
         */
        fun shuffle(): BytesTransformer {
            return ShuffleTransformer(Random)
        }

        /**
         * Create a [BytesTransformer] which shuffles the individual bytes in an array
         *
         * @param random to use for entropy
         * @return transformer
         */
        fun shuffle(random: Random): BytesTransformer {
            return ShuffleTransformer(random)
        }

        /**
         * Create a [BytesTransformer] which sorts the internal byte array with its natural ordering treating
         * each byte as signed byte (-128...127). Using inplace sorting, this can be reasonable fast.
         *
         * @return transformer
         */
        fun sort(): BytesTransformer {
            return SortTransformer()
        }

        /**
         * Create a [BytesTransformer] which sorts the internal byte array with its natural ordering treating
         * each byte as unsigned byte (0...255). That is, the byte string `ff` sorts after `00`.
         *
         *
         * **Note:** this requires 2 copies of the internal array and a lot of unboxing due to
         * the fact that no primitives are not allowed as generic type arguments - so only use on small arrays.
         *
         * @return transformer
         */
        fun sortUnsigned(): BytesTransformer {
            return SortTransformer(SortTransformer.UnsignedByteComparator())
        }

        /**
         * Create a [BytesTransformer] which sorts the internal byte array according to given comparator.
         *
         *
         * **Note:** this requires 2 copies of the internal array and a lot of unboxing due to
         * the fact that no primitives are not allowed as generic type arguments - so only use on small arrays.
         *
         * @param comparator to sort the bytes
         * @return transformer
         */
        fun sort(comparator: Comparator<Byte>?): BytesTransformer {
            return SortTransformer(comparator)
        }

        /**
         * Create a [BytesTransformer] which appends 4 byte Crc32 checksum to given bytes
         *
         * @return transformer
         */
//        fun checksumAppendCrc32(): BytesTransformer {
//            return ChecksumTransformer(CRC32(), ChecksumTransformer.Mode.APPEND, 4)
//        }

        /**
         * Create a [BytesTransformer] which transforms to 4 byte Crc32 checksum of given bytes
         *
         * @return transformer
         */
//        fun checksumCrc32(): BytesTransformer {
//            return ChecksumTransformer(CRC32(), ChecksumTransformer.Mode.TRANSFORM, 4)
//        }

        /**
         * Create a [BytesTransformer] which transforms to 4 byte Crc32 checksum of given bytes
         *
         * @param checksum           used algorithm
         * @param mode               mode (append or convert)
         * @param checksumLengthByte the byte length of the checksum; the [Checksum] class always returns 8 byte, but some
         * checksum algorithms (e.g. CRC32) only require smaller output. Must  be between 1 and 8 byte.
         * @return transformer
         */
//        fun checksum(checksum: Checksum?, mode: ChecksumTransformer.Mode?, checksumLengthByte: Int): BytesTransformer {
//            return ChecksumTransformer(checksum, mode, checksumLengthByte)
//        }


        /**
         * Shuffles the internal byte array
         */

        class ShuffleTransformer internal constructor(private val random: Random) : BytesTransformer {

            public override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray {
                val out = if (inPlace) currentArray else Bytes.from(currentArray).array()
                out.shuffle(random)
                return out
            }

            public override fun supportInPlaceTransformation(): Boolean {
                return true
            }
        }

        /**
         * Sorts the internal byte array with given [java.util.Comparator]
         */
        class SortTransformer @JvmOverloads internal constructor(private val comparator: Comparator<Byte>? = null) :
            BytesTransformer {
             override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray{
                if (comparator == null) {
                    val out = if (inPlace) currentArray else Bytes.from(currentArray).array()
                    //Arrays.sort(out)
                    out.sort()
                    return out
                } else {
                    //no in-place implementation with comparator
                    val boxedArray: Array<Byte> = Bytes.wrap(currentArray).toBoxedArray()
                    //Arrays.sort(boxedArray, comparator)
                    return boxedArray.sortedWith(comparator).toByteArray()
                }
            }

            public override fun supportInPlaceTransformation(): Boolean {
                return comparator == null
            }

            /**
             * Converting each byte into unsigned version and comparing it (0...255) vs (-128..127)
             */
            internal class UnsignedByteComparator : Comparator<Byte> {
                public override fun compare(o1: Byte, o2: Byte): Int {
                    val byteA = o1.toInt() and 0xff
                    val byteB = o2.toInt() and 0xff
                    if (byteA == byteB) return 0
                    return if (byteA < byteB) -1 else 1
                }
            }
        }

        /**
         * Adds or converts to arbitrary checksum
         */

//        class ChecksumTransformer internal constructor(checksum: Checksum, mode: Mode?, checksumLengthByte: Int) :
//            BytesTransformer {
//            private val checksum: Checksum
//            private val mode: Mode?
//            private val checksumLengthByte: Int
//
//            init {
//                require(!(checksumLengthByte <= 0 || checksumLengthByte > 8)) { "checksum length must be between 1 and 8 bytes" }
//
//                Objects.requireNonNull(checksum, "checksum instance must not be null")
//                this.checksum = checksum
//                this.mode = mode
//                this.checksumLengthByte = checksumLengthByte
//            }
//
//            public override fun transform(currentArray: ByteArray, inPlace: Boolean): ByteArray? {
//                checksum.update(currentArray, 0, currentArray.size)
//                val checksumBytes: ByteArray? = Bytes.from(checksum.getValue()).resize(checksumLengthByte).array()
//
//                if (mode == Mode.TRANSFORM) {
//                    return checksumBytes
//                } else {
//                    return Bytes.from(currentArray, checksumBytes!!).array()
//                }
//            }
//
//            public override fun supportInPlaceTransformation(): Boolean {
//                return false
//            }
//
//            /**
//             * Definitions of the mode
//             */
//            enum class Mode {
//                /**
//                 * Appends checksum to given byte array
//                 */
//                APPEND,
//
//                /**
//                 * Transforms byte array and returns only checksum
//                 */
//                TRANSFORM
//            }
//        }

    }
}
